﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using BrightIdeasSoftware;
using PDFPatcher.Common;
using PDFPatcher.Model;
using PDFPatcher.Processor;

namespace PDFPatcher.Functions.Editor
{
	sealed class Controller
	{
		public EditModel Model { get; private set; }
		public IEditView View { get; private set; }

		private BackgroundWorker _loader;

		public Controller (IEditView view) {
			Model = new EditModel ();
			View = view;
			Model.Undo.OnAddUndo += (UndoManager u, IUndoAction a) => View.UndoButton.Enabled = true;
			View.Bookmark.Undo = Model.Undo;
		}

		internal IEnumerable<XmlNode> ProcessBookmarks (IPdfInfoXmlProcessor processor) {
			return ProcessBookmarks (View.AffectsDescendantBookmarks, true, processor);
		}
		/// <summary>
		/// 逐个处理选中的书签。
		/// </summary>
		/// <param name="includeDescendant">处理操作是否包含选中书签的内层书签。</param>
		/// <param name="selectChildren">处理时是否遍历选中的内层书签。</param>
		/// <param name="processor">用于处理书签的 <see cref="IPdfInfoXmlProcessor"/>。</param>
		/// <returns>处理后的书签。</returns>
		internal IEnumerable<XmlNode> ProcessBookmarks (bool includeDescendant, bool selectChildren, IPdfInfoXmlProcessor processor) {
			var b = View.Bookmark;
			b.Freeze ();
			var si = b.GetSelectedElements (selectChildren);
			var pi = new HashSet<XmlElement>();
			var r = ProcessBookmarks (si, pi, includeDescendant, processor);
			if (r != null) {
				foreach (var item in r) {
					var i = item as XmlElement;
					if (i.ParentNode.Name == Constants.DocumentBookmark) {
						b.Roots = i.ParentNode.SelectNodes (Constants.Bookmark).ToXmlNodeArray ();
						break;
					}
				}
				b.RefreshObjects (r.ToArray ());
			}
			b.Unfreeze ();
			return r;
		}
		private HashSet<XmlNode> ProcessBookmarks (IList si, HashSet<XmlElement> processedItems, bool includeDescendant, IPdfInfoXmlProcessor processor) {
			if (si == null || si.Count == 0) {
				return null;
			}
			var undo = new UndoActionGroup ();
			foreach (BookmarkElement item in si) {
				ProcessItem (includeDescendant, processor, processedItems, undo, item);
			}
			if (undo.Count > 0) {
				Model.Undo.AddUndo (processor.Name, undo);
			}
			return new HashSet<XmlNode> (undo.AffectedElements);
		}

		private static void ProcessItem (bool includeDescendant, IPdfInfoXmlProcessor processor, HashSet<XmlElement> processedItems, UndoActionGroup undo, BookmarkElement item) {
			if (item == null || processedItems.Contains(item)) {
				return;
			}
			undo.Add (processor.Process (item));
			processedItems.Add(item);
			if (includeDescendant) {
				var dl = item.SubBookmarks;
				foreach (BookmarkElement d in dl) {
					ProcessItem (includeDescendant, processor, processedItems, undo, d);
				}
			}
		}

		internal XmlElement PrepareBookmarkDocument () {
			if (Model.Document == null) {
				Model.Document = new PdfInfoXmlDocument ();
			}
			return Model.Document.BookmarkRoot;
		}

		internal void ClearBookmarks () {
			Model.Document.BookmarkRoot.RemoveAll ();
			View.Bookmark.ClearObjects ();
		}

		private void LoadPdfDocument () {
			var s = Model.GetPdfFilePath ();
			var v = View.Viewer;
			if (s != null) {
				try {
					var d = v.Document;
					Model.PdfDocument = v.Document = new MuPdfSharp.MuDocument (s);
					d.TryDispose();
					View.AutoBookmark.TryDispose();
					v.Enabled = true;
					View.ViewerToolbar.Enabled = true;
				}
				catch (Exception ex) {
					FormHelper.ErrorBox (ex.Message);
					s = null;
				}
			}
			if (s == null) {
				v.Document.TryDispose();
				View.AutoBookmark.TryDispose();
				Model.PdfDocument = v.Document = null;
				v.Enabled = false;
				View.ViewerToolbar.Enabled = false;
			}
		}

		internal void Distroy () {
			//if (_view.InsertBookmarkForm != null) {
			//	_view.InsertBookmarkForm.Dispose ();
			//	_view.InsertBookmarkForm = null;
			//}
			if (_loader != null) {
				_loader.RunWorkerCompleted -= _LoadBookmarkWorker_RunWorkerCompleted;
				_loader.Dispose ();
			}
			Model.PdfDocument.TryDispose ();
			View.AutoBookmark.TryDispose();
		}

		internal void InitBookmarkEditor () {
			Model.Undo.Clear ();
			View.Bookmark.DeselectAll ();
			View.UndoButton.DropDown.Items.Clear ();
			View.UndoButton.Enabled = false;
			View.Bookmark.ClearMarks (false);
		}

		internal void LoadDocument (string path, bool importMode) {
			if (File.Exists (path) == false) {
				FormHelper.ErrorBox ("找不到文件：" + path);
				return;
			}
			var ext = Path.GetExtension (path).ToLowerInvariant ();
			var infoDoc = new PdfInfoXmlDocument ();
			switch (ext) {
				case Constants.FileExtensions.Txt:
					OutlineManager.ImportSimpleBookmarks (path, infoDoc);
					goto case "<load>";
				case Constants.FileExtensions.Xml:
					infoDoc.Load (path);
					goto case "<load>";
				case Constants.FileExtensions.Pdf:
					View.MainPanel.Enabled = View.BookmarkToolbar.Enabled = false;
					if (importMode == false) {
						View.DocumentPath = path;
						LoadPdfDocument ();
					}
					if (Model.PdfDocument == null) {
						return;
					}
					_loader = new BackgroundWorker();
					_loader.RunWorkerCompleted += _LoadBookmarkWorker_RunWorkerCompleted;
					_loader.DoWork += _LoadBookmarkWorker_DoWork;
					Model.IsLoadingDocument = true;
					_loader.RunWorkerAsync (new object[] { path, importMode });
					break;
				case "<load>":
					if (importMode) {
						LoadInfoDocument (infoDoc, importMode);
						break;
					}
					View.Bookmark.ClearObjects ();
					View.DocumentPath = path;
					LoadInfoDocument (infoDoc, importMode);
					LoadPdfDocument ();
					break;
				default:
					return;
			}
			RecentFileMenuHelper.AddRecentHistoryFile (path);
			//// 书签编辑器窗口需要重画表头
			//this._BookmarkBox.HeaderControl.Invalidate ();
		}

		private void _LoadBookmarkWorker_DoWork (object sender, DoWorkEventArgs e) {
			var args = e.Argument as object[];
			var path = args[0] as string;
			bool importMode = (bool)args[1];
			Tracker.DebugMessage ("open file");
			using (var reader = PdfHelper.OpenPdfFile (path, AppContext.LoadPartialPdfFile, false)) {
				try {
					Tracker.DebugMessage ("consolidate");
					reader.ConsolidateNamedDestinations ();
					Tracker.DebugMessage ("get bookmark");
					e.Result = new object[] {
					OutlineManager.GetBookmark (reader, new UnitConverter () { Unit = Constants.Units.Point }),
					importMode,
					path
				};
					Tracker.DebugMessage ("finished loading");
				}
				catch (iTextSharp.text.exceptions.BadPasswordException) {
					FormHelper.ErrorBox (Messages.PasswordInvalid);
					Tracker.TraceMessage (Tracker.Category.Error, Messages.PasswordInvalid);
				}
				catch (Exception ex) {
					FormHelper.ErrorBox ("在打开 PDF 文件时遇到错误：\n" + ex.Message);
				}
			}
		}

		private void _LoadBookmarkWorker_RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e) {
			((BackgroundWorker)sender).Dispose ();
			Model.IsLoadingDocument = false;
			View.MainPanel.Enabled = View.BookmarkToolbar.Enabled = true;
			var r = e.Result as object[];
			if (r == null) {
				// 异常终止
				ClearBookmarks ();
				InitBookmarkEditor ();
				return;
			}
			var b = r[0] as XmlElement;
			var importMode = (bool)r[1];
			if (importMode == false) {
				View.DocumentPath = r[2] as string;
			}
			if (b == null) {
				if (importMode) {
					FormHelper.InfoBox ("文档不包含书签。");
					return;
				}
				ClearBookmarks ();
				InitBookmarkEditor ();
				//if (_currentFilePath != null) {
				//	Common.FormHelper.InfoBox ("文档不包含书签。");
				//}
			}
			else {
				var infoDoc = new PdfInfoXmlDocument ();
				var root = infoDoc.DocumentElement;
				(root.AppendChild (infoDoc.CreateElement (Constants.Units.ThisName)) as XmlElement).SetAttribute (Constants.Units.Unit, Constants.Units.Point);
				root.AppendChild (infoDoc.ImportNode (b, true));
				LoadInfoDocument (infoDoc, importMode);
				if (importMode == false) {
					infoDoc.PdfDocumentPath = Model.DocumentPath;
				}
			}
			//_viewer = new PdfViewer (this._FilePathBox.Text, _doc.DocumentElement);
		}

		internal void LoadInfoDocument (PdfInfoXmlDocument document, bool importMode) {
			var b = View.Bookmark;
			var m = document.Bookmarks;
			if (importMode) {
				if (m.Count > 0) {
					ImportBookmarks (b, m);
				}
				return;
			}
			// 文档不包含书签
			if (m.Count == 0) {
				ClearBookmarks ();
				InitBookmarkEditor ();
				Model.Document = new PdfInfoXmlDocument ();
				PrepareBookmarkDocument ();
				return;
			}
			Model.Document = document;
			LoadBookmarks (b, m);
			if (Model.PdfDocument != null && document.PageLabelRoot.HasChildNodes) {
				var pl = Model.PdfDocument.PageLabels;
				pl.Clear ();
				foreach (PageLabelElement item in document.PageLabels) {
					pl.Add (item.ToPageLabel ());
				}
			}
		}

		private void LoadBookmarks (BookmarkEditorView view, XmlNodeList bookmarks) {
			InitBookmarkEditor ();
			view.LoadBookmarks (bookmarks);
		}

		private void ImportBookmarks (BookmarkEditorView editView, XmlNodeList bookmarks) {
			if (Model.Document == null) {
				Model.Document = new PdfInfoXmlDocument ();
			}
			var d = Model.Document;
			var g = new UndoActionGroup ();
			var s = editView.GetFirstSelectedModel<BookmarkElement> ();
			var il = new List<XmlNode> ();
			XmlElement n;
			if (s != null) {
				foreach (XmlNode item in bookmarks) {
					n = s.AppendChild (d.ImportNode (item, true)) as XmlElement;
					g.Add (new RemoveElementAction (n));
					il.Add (n);
				}
				editView.RefreshObject (s);
				editView.Expand (s);
			}
			else {
				var r = d.BookmarkRoot;
				foreach (XmlNode item in bookmarks) {
					n = r.AppendChild (d.ImportNode (item, true)) as XmlElement;
					g.Add (new RemoveElementAction (n));
					il.Add (n);
				}
				editView.Roots = d.Bookmarks;
			}
			editView.SelectedObjects = il;
			Model.Undo.AddUndo ("导入书签", g);
		}

		//internal void HideInsertBookmarkForm () {
		//	if (_view.InsertBookmarkForm != null) {
		//		_view.InsertBookmarkForm.Visible = false;
		//	}
		//}

		//internal void BookmarkAtClientPoint (Point cp) {
		//	var v = _view.Viewer;
		//	var pp = v.TransposeClientToPagePosition (cp.X, cp.Y);
		//	if (pp.Page == 0) {
		//		return;
		//	}
		//	if (Control.ModifierKeys == Keys.Control) {
		//		v.PinPoint = v.PointToImage (cp);
		//		ShowInsertBookmarkDialog (cp, new EditModel.Region (pp, null, EditModel.TextSource.Empty));
		//		return;
		//	}
		//	var r = CopyText (cp, pp);
		//	ShowInsertBookmarkDialog (cp, r);
		//}

		internal EditModel.Region CopyText (Point cp, PagePosition pp) {
			var v = View.Viewer;
			var ps = v.IsClientPointInSelection (cp);
			var lines = ps ? v.FindTextLines (v.GetSelectionPageRegion ()) : v.FindTextLines (pp).TextLines;
			string t = null;
			EditModel.TextSource ts;
			if (Model.InsertBookmarkWithOcrOnly == false && lines.HasContent ()) {
				var sb = new StringBuilder ();
				var r = lines[0].BBox;
				foreach (var line in lines) {
					if (sb.Length > 100) {
						break;
					}
					t = line.Text.TrimEnd ();
					if (sb.Length > 0 && t.Length > 0) {
						var c = t[0];
						sb.Append (' ');
						r = r.Union (line.BBox);
					}
					sb.Append (t);
				}
				t = sb.ToString ();
				var b = v.MuRectangleToImageRegion (pp.Page, r);
				v.SelectionRegion = b;
				v.PinPoint = b.Location.Round ();
				b.Offset (v.GetVirtualImageOffset (pp.Page));
				pp = v.TransposeVirtualImageToPagePosition (pp.Page, v.PinPoint.X, v.PinPoint.Y);
				ts = EditModel.TextSource.Text;
			}
			else if (t == null && ModiOcr.ModiInstalled && v.OcrLanguage != 0) {
				v.UseWaitCursor = true;
				var r = v.OcrPage (pp.Page, true);
				v.UseWaitCursor = false;
				if (r.HasContent ()) {
					var ir = v.GetSelection ().ImageRegion;
					var ib = new Bound (ir.Left, ir.Bottom, ir.Right, ir.Top);
					var b = RectangleF.Empty;
					if (ps) {
						var mr = r.FindAll ((i) => i.Region.IntersectWith(ib));
						if (mr.HasContent ()) {
							var sb = new StringBuilder ();
							b = mr[0].Region;
							foreach (var line in mr) {
								t = OcrProcessor.CleanUpText (line.Text, v.OcrOptions);
								if (sb.Length > 0 && t.Length > 0) {
									var c = t[0];
									sb.Append (' ');
									b = b.Union (line.Region);
								}
								sb.Append (t);
							}
							t = sb.ToString ();
						}
					}
					else {
						var l = v.TransposeClientToPageImage (cp.X, cp.Y);
						var tl = r.Find ((i) => i.Region.Contains(l.ImageX, l.ImageY));
						if (tl != null) {
							t = tl.Text;
							b = (RectangleF)tl.Region;
						}
					}
					if (b != RectangleF.Empty) {
						b.Offset (v.GetVirtualImageOffset (pp.Page));
						v.SelectionRegion = b;
						v.PinPoint = b.Location.Round ();
						pp = v.TransposeVirtualImageToPagePosition (pp.Page, b.Left.ToInt32 (), b.Top.ToInt32 ());
						ts = EditModel.TextSource.OcrText;
					}
					else {
						ts = EditModel.TextSource.Empty;
					}
				}
				else {
					ts = EditModel.TextSource.OcrError;
				}
			}
			else {
				v.PinPoint = v.PointToImage (cp);
				ts = EditModel.TextSource.Empty;
			}
			return new EditModel.Region (pp, t, ts);
		}

		//private void ShowInsertBookmarkDialog (Point mousePoint, EditModel.Region region) {
		//	var p = region.Position;
		//	if (p.Page == 0) {
		//		return;
		//	}
		//	if (_view.InsertBookmarkForm == null) {
		//		_view.InsertBookmarkForm = new InsertBookmarkForm ();
		//		_view.InsertBookmarkForm.OkClicked += _insertBookmarkForm_OkClicked;
		//		_view.InsertBookmarkForm.VisibleChanged += (s, args) => {
		//			_view.Viewer.ShowPinPoint = (s as Form).Visible;
		//		};
		//	}
		//	var f = _view.InsertBookmarkForm;
		//	var v = _view.Viewer;
		//	var vp = v.GetImageViewPort ();
		//	Point fp;
		//	var sr = v.SelectionRegion;
		//	if (sr != RectangleF.Empty) {
		//		fp = v.TransposeVirtualImageToClient (sr.Left, sr.Top);
		//		if (v.HorizontalFlow) {
		//			fp.X += sr.Width.ToInt32 () + 20;
		//		}
		//		else {
		//			fp.Y -= f.Height + 20;
		//		}
		//	}
		//	else {
		//		fp = new Point (mousePoint.X + 20, mousePoint.Y - f.Height);
		//	}
		//	var l = v.PointToScreen (fp);
		//	if (l.Y < 0) {
		//		l.Y = l.Y + (int)sr.Height + f.Height + 40;
		//		if (l.Y + f.Height > Screen.PrimaryScreen.WorkingArea.Height) {
		//			l.Y = Screen.PrimaryScreen.WorkingArea.Height - f.Height;
		//		}
		//	}
		//	if (l.X < v.PointToScreen (Point.Empty).X) {
		//		l.X = v.PointToScreen (Point.Empty).X;
		//	}
		//	f.Location = l;
		//	f.TargetPosition = p.PageY;
		//	if (String.IsNullOrEmpty (region.Text) == false) {
		//		f.Title = __RemoveOcrWhiteSpace.Replace (region.Text, " ").Trim ();
		//	}
		//	f.Comment = region.LiteralTextSource;
		//	f.Show ();
		//	f.TargetPageNumber = p.Page;
		//	f.FormClosed += (s1, a1) => { _view.InsertBookmarkForm = null; };
		//}

		internal void LabelAtPage (Editor.PagePosition position) {
			if (position.Page == 0) {
				return;
			}
			var l = Model.PdfDocument.PageLabels;
			if (l == null) {
				return;
			}
			var v = View.Viewer;
			var f = new InsertPageLabelForm {
				Location = Cursor.Position.Transpose (-16, -16),
				PageNumber = position.Page
			};
			var pl = l.Find (position.Page);
			if (pl.IsEmpty == false) {
				f.SetValues (pl);
			}
			f.FormClosed += InsertPageLabelForm_Closed;
			f.Show ();
		}

		void InsertPageLabelForm_Closed (object sender, EventArgs e) {
			var form = sender as InsertPageLabelForm;
			if (form.DialogResult == DialogResult.Cancel) {
				return;
			}
			var l = Model.PdfDocument.PageLabels;
			if (form.DialogResult == DialogResult.OK) {
				if (l == null) {
					return;
				}
				l.Add (form.PageLabel);
			}
			else if (form.DialogResult == DialogResult.Abort) {
				Model.PdfDocument.PageLabels.Remove (form.PageLabel);
			}
			var pl = Model.Document.PageLabelRoot;
			pl.InnerText = String.Empty;
			foreach (var item in l) {
				pl.AppendChild (Model.Document.CreatePageLabel (item));
			}
			View.Viewer.Invalidate ();
		}

		internal void InsertBookmark () {
			if (Model.PdfDocument != null) {
				var pn = View.Viewer.CurrentPageNumber;
				var p = View.Viewer.TransposeClientToPagePosition (0, 0).PageY;
				var pt = View.Viewer.GetPageBound (pn).Bottom;
				if (pt < p) {
					p = pt;
				}
				InsertBookmark (null, pn, p, InsertBookmarkPositionType.AfterCurrent);
			}
			else {
				InsertBookmark (null, 0, 0, InsertBookmarkPositionType.AfterCurrent);
			}
		}

		internal void InsertBookmark (string title, int pageNumber, float position, InsertBookmarkPositionType type) {
			var b = View.Bookmark;
			var d = Model.Document;
			int i = b.SelectedIndex;
			BookmarkElement c = null;
			if (i == -1 && b.Items.Count == 0) {
				c = d.BookmarkRoot.AppendBookmark();
				Model.Undo.AddUndo ("插入书签", new RemoveElementAction (c));
				var s = title ?? Path.GetFileNameWithoutExtension (Model.DocumentPath);
				c.SetTitleAndGotoPagePosition (
					string.IsNullOrEmpty (s) ? Constants.Bookmark : s,
					pageNumber > 0 ? pageNumber : 1,
					position);
				b.Roots = new XmlElement[] { c };
			}
			else {
				var o = b.GetModelObject (i != -1 ? i : b.GetItemCount () - 1) as BookmarkElement;
				var t = title ?? Constants.Bookmark;
				var p = pageNumber > 0 ? pageNumber : o.Page;
				if (type == InsertBookmarkPositionType.NoDefined) {
					var g = new UndoActionGroup ();
					g.SetAttribute (o, Constants.BookmarkAttributes.Title, t);
					g.SetAttribute (o, Constants.DestinationAttributes.Action, Constants.ActionType.Goto);
					g.SetAttribute (o, Constants.DestinationAttributes.Page, p.ToText ());
					g.SetAttribute (o, Constants.DestinationAttributes.View, Constants.DestinationAttributes.ViewType.XYZ);
					g.SetAttribute (o, Constants.Coordinates.Top, position.ToText ());
					Model.Undo.AddUndo ("替换书签", g);
					c = o;
				}
				else {
					c = d.CreateBookmark ();
					c.SetTitleAndGotoPagePosition (t, p, position);
					switch (type) {
						case InsertBookmarkPositionType.AfterCurrent:
							goto default;
						case InsertBookmarkPositionType.AsChild:
							o.AppendChild (c);
							break;
						case InsertBookmarkPositionType.AfterParent:
							if (o.ParentBookmark != null) {
								o.ParentBookmark.ParentNode.InsertAfter (c, o.ParentBookmark);
							}
							else {
								goto default;
							}
							break;
						case InsertBookmarkPositionType.BeforeCurrent:
							o.ParentNode.InsertBefore (c, o);
							break;
						default:
							o.ParentNode.InsertAfter (c, o);
							break;
					}
					Model.Undo.AddUndo ("插入书签", new RemoveElementAction (c));
				}
				if (c.ParentNode.Name == Constants.DocumentBookmark) {
					b.SetObjects (c.Parent.SubBookmarks);
				}
				else {
					b.RefreshObject (c.ParentNode);
					b.RefreshObject (c);
				}
			}
			if (c != null) {
				if (c.ParentNode.Name == Constants.Bookmark) {
					b.Expand (c.ParentNode);
				}
				b.Expand (c);
				b.EnsureItemsVisible (new BookmarkElement[] { c });
				b.SelectedObjects = new BookmarkElement[] { c };
				b.FocusedObject = c;
				//_BookmarkBox.ModelToItem (c).BeginEdit ();
			}
		}

		internal void Undo (int step) {
			if (Model.Undo.CanUndo == false) {
				return;
			}
			Model.LockDownViewer = true;
			var sl = View.Bookmark.SelectedObjects;
			XmlElement e;
			bool r = false;	// 是否需要刷新根节点
			var rl = new HashSet<XmlNode> ();
			while (step-- > 0) {
				var a = Model.Undo.Undo ();
				foreach (var item in a) {
					e = item as XmlElement;
					if (r == false && e.Name == Constants.DocumentBookmark) {
						r = true;
					}
					else {
						rl.Add (item);
					}
				}
			}

			View.Bookmark.RefreshObjects (rl.ToArray ());
			if (r) {
				View.Bookmark.Roots = Model.Document.Bookmarks;
			}
			View.Bookmark.SelectedObjects = sl;
			View.UndoButton.Enabled = Model.Undo.CanUndo;
			Model.LockDownViewer = false;
		}

		internal void MergeBookmark (IList<BookmarkElement> es) {
			var l = es.Count;
			if (l < 2) {
				return;
			}
			var p = es[0].ParentNode;
			for (int i = 1; i < l; i++) {
				//if (es[i].SelectSingleNode (Constants.Bookmark) != null) {
				//    Common.Form.ErrorBox ("合并的书签不能有子书签。");
				//    return;
				//}
				if (es[i].ParentNode != p && es[i].ParentNode != es[0]) {
					FormHelper.ErrorBox ("合并的书签必须有相同的上级书签。");
					return;
				}
			}
			var undo = new UndoActionGroup ();
			var ts = new string[l];
			var dest = es[0];
			ts[0] = dest.Title;
			var ct = dest.OwnerDocument.CreateDocumentFragment ();
			for (int i = 1; i < l; i++) {
				ts[i] = es[i].GetAttribute (Constants.BookmarkAttributes.Title);
				if (ts[i].Length > 0) {
					var c = ts[i][0];
					if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {
						ts[i] = " " + ts[i];
					}
				}
				while (es[i].HasChildNodes) {
					var c = es[i].FirstChild as XmlElement;
					if (c == null) {
						continue;
					}
					undo.Add (new AddElementAction (c));
					ct.AppendChild (c);
				}
				if (es[i].ParentNode == p) {
					undo.Add (new AddElementAction (es[i]));
					p.RemoveChild (es[i]);
				}
				else /*es[i].ParentNode = es[0]*/ {
					undo.Add (new AddElementAction (es[i]));
					dest.RemoveChild (es[i]);
				}
			}
			while (ct.HasChildNodes) {
				dest.AppendChild (ct.FirstChild);
			}
			undo.Add (UndoAttributeAction.GetUndoAction (dest, Constants.BookmarkAttributes.Title, String.Concat (ts)));
			var b = View.Bookmark;
			if (p.Name != Constants.DocumentBookmark) {
				b.RefreshObject (p);
			}
			else {
				b.SetObjects (p.SelectNodes (Constants.Bookmark));
			}
			Model.Undo.AddUndo ("合并书签", undo);
			b.RefreshObject (dest);
			b.SelectObject (dest);
		}


		internal void ConfigAutoBookmarkTextStyles (int level, Editor.TextInfo textInfo) {
			if (textInfo.Spans == null) {
				return;
			}
			foreach (var span in textInfo.Spans) {
				var s = textInfo.Page.GetFont(span);
				if (s != null) {
					bool m = false;
					int fs = span.Size.ToInt32();
					foreach (var item in Model.TitleStyles) {
						if (item.Font.Name == s.Name && item.FontSize == fs) {
							m = true;
							goto NEXT;
						}
					}
					if (m == false) {
						Model.TitleStyles.Add(new EditModel.AutoBookmarkStyle(level, s, fs));
					}
				}
				NEXT:;
			}
			ShowAutoBookmarkForm();
		}

		internal void ShowAutoBookmarkForm() {
			var f = View.AutoBookmark;
			if (f.Visible == false) {
				f.Location = Cursor.Position.Transpose(-16, -16);
				f.Show(View.Viewer);
			}
			f.SetValues(Model.TitleStyles);
		}

		internal void AutoBookmark (IEnumerable<EditModel.AutoBookmarkStyle> list, bool mergeAdjacentTitle) {
			View.Bookmark.CancelCellEdit();
			var pdf = Model.PdfDocument;
			BookmarkContainer bm = Model.Document.BookmarkRoot;
			var c = pdf.PageCount;
			var bs = new List<EditModel.AutoBookmarkStyle>(list);
			if (bs.Count == 0) {
				return;
			}
			bs.Sort((x, y) => x.Level - y.Level);
			var mp = new MatchPattern.IMatcher[bs.Count];
			for (var i = 0; i < bs.Count; i++) {
				mp[i] = bs[i].MatchPattern?.CreateMatcher();
			}
			var ug = new UndoActionGroup();
			Model.Undo.AddUndo("自动生成书签", ug);
			foreach (XmlElement item in bm.SubBookmarks) {
				ug.Add(new AddElementAction(item));
			}
			bm.RemoveAll();
			var spans = new List<MuPdfSharp.MuTextSpan>(3);
			var bl = 0;
			for (int i = 0; i < c;) {
				using (var p = pdf.LoadPage (++i)) {
					var h = p.VisualBound.Height;
					var dh = p.VisualBound.Bottom - h;
					foreach (var block in p.TextPage.Blocks) {
						foreach (var line in block.Lines) {
							foreach (var span in line.Spans) {
								for (var si = 0; si < bs.Count; si++) {
									var style = bs[si];
									var matcher = mp[si];
									if (style.FontName != PdfDocumentFont.RemoveSubsetPrefix(p.GetFont(span).Name)
										|| style.FontSize != span.Size.ToInt32()) {
										continue;
									}
									var t = span.Text;
									var b = span.Box;
									if (t.Length == 0) {
										continue;
									}
									if (bl < style.Level) {
										if (matcher?.Matches(line.Text) == false) {
											continue;
										}
										bm = CreateNewSiblingBookmark(bm, spans);
										++bl;
									}
									else if (bl == style.Level) {
										// todo+ 删除重复的文本
										var cb = bm as BookmarkElement;
										var bb = h - cb.Bottom + dh;
										var bt = h - cb.Top;
										var lt = b.Top - b.Height * 2 + dh;
										var lb = b.Bottom;
										if (cb.Page == p.PageNumber
											&& (bb >= lt && bb <= lb || bt >= lt && bt <= lb || bt < lt && bb > lb)
											&& (mergeAdjacentTitle || spans.Count == 1 || spans[spans.Count - 1].Point.Y == span.Point.Y)) {
											//var m = false;
											//var bs = b.Size.Area();
											//foreach (var ss in spans) {
											//	var a = ss.Box.Intersect(b).Size.Area();
											//	var ov = a / bs;
											//	if (0.5 < ov && ov <= 1) {
											//		m = true;
											//		break;
											//	}
											//	ov = a / ss.Box.Size.Area();
											//	if (0.5 < ov && ov <= 1) {
											//		m = true;
											//		break;
											//	}
											//}
											if (/*m == false &&*/ t.Length > 0) {
												// 保留英文和数字文本之间的空格
												var ct = cb.Title;
												if (ct.Length > 0) {
													var lc = ct[ct.Length - 1];
													cb.Title = (Char.IsLetterOrDigit(lc) || Char.IsPunctuation(lc)
															&& lc != '-') && t[0] != ' '
														? ct + ' ' + t
														: ct + t;
												}
												cb.Bottom = h - lb;
												spans.Add(span);
											}
											continue;
										}
										if (matcher?.Matches(line.Text) == false) {
											continue;
										}
										bm = CreateNewSiblingBookmarkForParent(bm, spans);
									}
									else {
										while (bl > style.Level) {
											bm = bm.ParentBookmark;
											--bl;
										}
										if (matcher?.Matches(line.Text) == false) {
											continue;
										}
										bm = CreateNewSiblingBookmarkForParent(bm, spans);
									}
									var be = bm as BookmarkElement;
									be.Title = t;
									be.Top = h - b.Top + b.Height + dh;
									be.Bottom = h - b.Bottom + dh;
									be.Action = Constants.ActionType.Goto;
									be.Page = p.PageNumber;
									var s = style.Style;
									if (s.IsBold || s.IsItalic) {
										be.TextStyle = s.IsBold && s.IsItalic ? FontStyle.Bold | FontStyle.Italic
											: s.IsBold ? FontStyle.Bold
											: s.IsItalic ? FontStyle.Italic
											: FontStyle.Regular;
									}
									if (s.IsOpened) {
										be.IsOpen = true;
									}
									be.ForeColor = s.ForeColor;
									//todo+ 删除尾随的空格
									ug.Add(new RemoveElementAction(bm));
									spans.Add(span);
									break;
								}
							}
						}
					}
				}
			}
			View.Bookmark.Roots = Model.Document.Bookmarks;
			View.Bookmark.RebuildAll(false);
		}

		static BookmarkContainer CreateNewSiblingBookmarkForParent(BookmarkContainer bm, List<MuPdfSharp.MuTextSpan> spans) {
			TrimBookmarkText(bm);
			bm = bm.Parent.AppendBookmark();
			spans.Clear();
			return bm;
		}

		static void TrimBookmarkText(BookmarkContainer bm) {
			if (bm is BookmarkElement b) {
				var t = b.Title;
				var t2 = b.Title.Trim();
				if (t2 != t) {
					b.Title = t2;
				}
			}
		}

		static BookmarkContainer CreateNewSiblingBookmark(BookmarkContainer bm, List<MuPdfSharp.MuTextSpan> spans) {
			TrimBookmarkText(bm);
			bm = bm.AppendBookmark();
			spans.Clear();
			return bm;
		}
	}
}
